﻿using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Collections;
using System.Diagnostics;

public class BullsAndCows
{
    static Random rand = new Random();

    #region Fields

    private uint cowsCount;
    private uint bullsCount;
    private uint attemps;
    private int cheats;
    private uint[] secretNumberArray;
    private int secretNumber;
    private StringBuilder helpString;
    private const string FileName = "result.dat";
    private const string welcomeString = @"Welcome to “Bulls and Cows” game. Please try to guess my secret 4-digit number.
Use 'top' to view the top scoreboard, 'restart' to start a new game and 'help' to cheat and 'exit' to quit the game.";

    #endregion
    #region Attributes

    public uint CowsCount
    {
        get
        {
            return cowsCount;
        }
        set
        {
            cowsCount = value;
        }
    }

    public uint BullsCount
    {
        get
        {
            return bullsCount;
        }
        set
        {
            bullsCount = value;
        }
    }

    public uint Attemps
    {
        get
        {
            return attemps;
        }
        set
        {
            attemps = value;
        }
    }

    public uint[] SecretNumberArray
    {
        get
        {
            return secretNumberArray;
        }
        set
        {
            secretNumberArray = value;
        }
    }

    public int SecretNumber
    {
        get
        {
            return secretNumber;
        }
        set
        {
            secretNumber = value;
        }
    }

    #endregion

    public BullsAndCows()
    {
        this.cowsCount = 0;
        this.bullsCount = 0;
        this.attemps = 0;
        this.cheats = 0;
        this.helpString = new StringBuilder("XXXX");
    }

    public void GenerateSecretNumber()
    {
        this.secretNumber = rand.Next(1000, 9999);
    }

    public void CalculateSecretNumber(int number)
    {
        secretNumberArray = new uint[4];
        for (int i = 3; i >= 0; i--)
        {
            int digit = number % 10;
            secretNumberArray[i] = (uint)digit;
            number = number / 10;
        }
    }

    public uint[] RepresentGuestNumberInArray(int guessNumber)
    {
        uint[] guestNumberArray = new uint[4];
        for (int i = 3; i >= 0; i--)
        {
            int digit = guessNumber % 10;
            guestNumberArray[i] = (uint)digit;
            guessNumber = guessNumber / 10;
        }

        return guestNumberArray;
    }

    public void GetBulls(uint[] guestNumberArray)
    {
        for (int i = 0; i < 4; i++)
        {
            if (guestNumberArray[i] == this.secretNumberArray[i])
            {
                this.bullsCount++;
                guestNumberArray[i] = 10; // a not possible number to not be taken in account twice
            }
        }
    }

    public void GetCows(uint[] guestNumberArray)
    {
        HashSet<uint> numbers = new HashSet<uint>();
        for (int i = 0; i < 4; i++)
        {
            numbers.Add(this.secretNumberArray[i]);
        }
        for (int i = 0; i < 4; i++)
        {
            if (numbers.Contains(guestNumberArray[i]))
            {
                this.cowsCount++;
            }
        }
    }

    public bool IsGuestNumberRight(int guestNumber)
    {
        this.attemps++;
        this.bullsCount = 0;
        this.cowsCount = 0;

        if (this.secretNumber == guestNumber)
            return true;

        uint[] guestNumberArray = RepresentGuestNumberInArray(guestNumber);

        GetBulls(guestNumberArray);
        GetCows(guestNumberArray);

        return false;
    }

    public string GetSecretDigit()
    {
        this.cheats++;
        while (true)
        {
            int index = rand.Next(0, 4);
            if (this.helpString[index] == 'X')
            {
                this.helpString[index] = this.secretNumberArray[3 - index].ToString()[0];
                break;
            }
        }

        return this.helpString.ToString();
    }

    public void ResetGame()
    {
        GenerateSecretNumber();
        this.CalculateSecretNumber(this.secretNumber);
        this.cowsCount = 0;
        this.bullsCount = 0;
        this.attemps = 0;
        this.cheats = 0;
        this.helpString = new StringBuilder("XXXX");
    }



    public bool PlayGame()
    {
        this.GenerateSecretNumber();

        this.CalculateSecretNumber(this.secretNumber);
        Console.WriteLine(welcomeString + this.secretNumber);
        while (true)
        {
            string input = Console.ReadLine();
            switch (input)
            {
                case "exit":
                    string result = "Good bye!";
                    Console.WriteLine(result); return true;
                case "help":
                    Console.WriteLine(this.GetSecretDigit());
                    if (this.cheats == 4)
                    {
                        Console.WriteLine("Congratulations! You guessed the secret number in {0} attempts and {1} cheats.",
                            this.attemps, this.cheats);
                        this.ResetGame();
                        Console.WriteLine(welcomeString + this.secretNumber);
                    }
                    break;
                case "restart":
                    this.ResetGame();
                    Console.WriteLine(welcomeString + this.secretNumber);
                    break;
                case "top":
                    Console.WriteLine(this.GetScoreBoard()); break;
                default:
                    PlayWithNumber(input); break;
            }
        }
    }

    public void PlayWithNumber(string input)
    {
        int guessNumber = int.MinValue;
        bool isNumber = int.TryParse(input, out guessNumber);
        if (!isNumber || input.Length != 4 && !(guessNumber >= 1000 && guessNumber <= 9999))
        {
            Console.WriteLine("Incorrect guess or command!");
            return;
        }
        bool win = this.IsGuestNumberRight(guessNumber);
        this.RepresentGuestNumberInArray(guessNumber);

        if (win)
        {
            Console.WriteLine("Congratulations! ! You guessed the secret number in {0} attempts and {1} cheats.", 
                this.attemps, this.cheats);
            if (this.cheats == 0)
            {
                Console.Write("Please enter your name for top scoreboard: ");
                string name = Console.ReadLine();
                this.SavePlayer(name);
            }

            Console.WriteLine(this.GetScoreBoard());
            this.ResetGame();
            this.CalculateSecretNumber(this.secretNumber);
            Console.WriteLine();
            Console.WriteLine(welcomeString + this.secretNumber);
        }
        else
        {
            Console.WriteLine("Wrong number! Bulls: {0}, Cows: {1}", this.bullsCount, this.cowsCount);
        }
    }


    public string GetScoreBoard()
    {
        StringBuilder result = new StringBuilder();
        if (File.Exists(FileName))
        {
            using (FileStream fs = new FileStream(FileName, FileMode.Open))
            {
                using (BinaryReader reader = new BinaryReader(fs))
                {
                    while (reader.PeekChar() != -1)
                    {
                        result.AppendFormat("{0} --> {1} guesses", reader.ReadString(), reader.ReadInt32());
                        result.AppendLine();
                    }
                }
            }
        }

        if (string.IsNullOrEmpty(result.ToString()))
        {
            return "The scoreboard is empty.";
        }

        return "Scoreboard: \n" + result.ToString();

    }

    public void SavePlayer(string name)
    {
        List<Player> playersList = new List<Player>();

        using (FileStream fs = new FileStream(FileName, FileMode.OpenOrCreate))
        {
            using (BinaryReader reader = new BinaryReader(fs))
            {
                while (reader.PeekChar() != -1)
                {
                    playersList.Add(new Player(reader.ReadString(), reader.ReadUInt32()));
                }
            }
        }

        playersList.Add(new Player(name, this.attemps));
        playersList.Sort(new PlayerComparer());

        uint maxValue = playersList[playersList.Count - 1].guesses;
        if (playersList.Count > 5)
            maxValue = playersList[4].guesses;

        using (FileStream file = new FileStream(FileName, FileMode.Create))
        {
            using (BinaryWriter writer = new BinaryWriter(file))
            {
                for (int i = 0; i < playersList.Count; i++)
                {
                    if (playersList[i].guesses > maxValue)
                    {
                        break;
                    }

                    writer.Write(playersList[i].name);
                    writer.Write(playersList[i].guesses);
                }
            }
        }
    }

    public static void Play()
    {
        BullsAndCows game = new BullsAndCows();
        if (game.PlayGame())
            return;
    }

    public static void Main()
    {
        Play();
    }
}
